﻿using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using FFmpeg.AutoGen;
using PropertyChanged;

namespace FFmpegVideoClip.Lib
{
    [AddINotifyPropertyChangedInterface]
    public class FFmpegDevices
    {
        public sealed unsafe class HardwareDeviceInfo
        {
            /// <summary>
            /// Initializes a new instance of the <see cref="HardwareDeviceInfo"/> class.
            /// </summary>
            /// <param name="config">The source configuration.</param>
            internal HardwareDeviceInfo(AVCodecHWConfig* config)
            {
                DeviceType = config->device_type;
                PixelFormat = config->pix_fmt;
                DeviceTypeName = ffmpeg.av_hwdevice_get_type_name(DeviceType);
                PixelFormatName = ffmpeg.av_get_pix_fmt_name(PixelFormat);
            }

            /// <summary>
            /// Gets the type of hardware device.
            /// </summary>
            public AVHWDeviceType DeviceType { get; }

            /// <summary>
            /// Gets the name of the device type.
            /// </summary>
            public string DeviceTypeName { get; }

            /// <summary>
            /// Gets the hardware output pixel format.
            /// </summary>
            public AVPixelFormat PixelFormat { get; }

            /// <summary>
            /// Gets the name of the pixel format.
            /// </summary>
            public string PixelFormatName { get; }

            /// <summary>
            /// Returns a <see cref="string" /> that represents this instance.
            /// </summary>
            /// <returns>
            /// A <see cref="string" /> that represents this instance.
            /// </returns>
            public override string ToString()
            {
                return $"Device {DeviceTypeName}: {PixelFormatName}";
            }
        }

        private static readonly AVHWDeviceType[] m_SupportDecodingDevices = GetSupportDecodingDevice();
        public static AVHWDeviceType[] SupportDecodingDevices => m_SupportDecodingDevices;
        public unsafe static AVHWDeviceType[] GetSupportDecodingDevice()
        {
            try
            {
                List<AVHWDeviceType> SupportDecodingDevices = new();
                SupportDecodingDevices.Add(AVHWDeviceType.AV_HWDEVICE_TYPE_NONE);
                AVHWDeviceType type = AVHWDeviceType.AV_HWDEVICE_TYPE_NONE;
                AVBufferRef* devContextRef = null;
                int initResultCode;
                while ((type = ffmpeg.av_hwdevice_iterate_types(type)) != AVHWDeviceType.AV_HWDEVICE_TYPE_NONE)
                {
                    initResultCode = ffmpeg.av_hwdevice_ctx_create(&devContextRef, type, null, null, 0);
                    ffmpeg.av_buffer_unref(&devContextRef);
                    if (initResultCode < 0)
                        continue;
                    SupportDecodingDevices.Add(type);
                }

                return SupportDecodingDevices.ToArray();
            }
            catch
            {
                return [AVHWDeviceType.AV_HWDEVICE_TYPE_NONE];
            }
            
        }
        public unsafe static List<HardwareDeviceInfo> GetCompatibleDevices(AVCodecID codecId)
        {
            const int AV_CODEC_HW_CONFIG_METHOD_HW_DEVICE_CTX = 0x01;
            var codec = ffmpeg.avcodec_find_decoder(codecId);
            var result = new List<HardwareDeviceInfo>(64);
            var configIndex = 0;

            // skip unsupported configs
            if (codec == null || codecId == AVCodecID.AV_CODEC_ID_NONE)
                return result;

            while (true)
            {
                var config = ffmpeg.avcodec_get_hw_config(codec, configIndex);
                if (config == null) break;

                if ((config->methods & AV_CODEC_HW_CONFIG_METHOD_HW_DEVICE_CTX) != 0
                    && config->device_type != AVHWDeviceType.AV_HWDEVICE_TYPE_NONE)
                {
                    result.Add(new HardwareDeviceInfo(config));
                }

                configIndex++;
            }

            return result;
        }

        
    }

    public unsafe class PixelFormatGet
    {
        public AVCodecContext_get_format GetFormatCallback;
        public AVPixelFormat PixelFormat;
        public PixelFormatGet(AVPixelFormat PixelFormat)
        {
            this.PixelFormat = PixelFormat;
            GetFormatCallback = GetPixelFormat;
        }
        public AVPixelFormat GetPixelFormat(AVCodecContext* context, AVPixelFormat* pixelFormats)
        {
            // The default output is the first pixel format found.
            var output = *pixelFormats;

            // Iterate throughout the different pixel formats provided by the codec
            for (var p = pixelFormats; *p != AVPixelFormat.AV_PIX_FMT_NONE; p++)
            {
                // Try to select a hardware output pixel format that matches the HW device
                if (*p == PixelFormat)
                {
                    output = PixelFormat;
                    break;
                }

                // Otherwise, just use the default SW pixel format
                output = *p;
            }

            // Return the current pixel format.
            return output;
        }
    }

    internal static class FFmpegHelper
    {
        public static unsafe string av_strerror(int error)
        {
            int bufferSize = 1024;
            byte* buffer = stackalloc byte[bufferSize];
            ffmpeg.av_strerror(error, buffer, (ulong)bufferSize);
            string? message = Marshal.PtrToStringAnsi((IntPtr)buffer);
            return $"ErrorCode:[{error}],Message:{message ?? string.Empty}";
        }
        public static int ThrowExceptionIfError(this int error)
        {
            if (error < 0) throw new ApplicationException(av_strerror(error));
            return error;
        }
    }

}



